package org.xhy.retryer.core;

import org.xhy.retryer.lister.RetryerLister;
import org.xhy.retryer.predicate.IPredicate;
import org.xhy.retryer.strategy.RetryerStrategy;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Predicate;

/**
 * @description:
 * @gitee: https://gitee.com/XhyQAQ
 * @copyright: B站: https://space.bilibili.com/152686439
 * @Author: Xhy
 * @CreateTime: 2024-04-29 21:33
 */
public class Retryer {

    private RetryerStrategy retryerStrategy;

    private Predicate predicateResult;

    private Predicate predicateThrowsException;

    private Executor executor;


    public Retryer(RetryerStrategy retryerStrategy, Predicate predicateResult, Predicate predicateThrowsException, RetryerLister retryerLister,Executor executor) {
        this.retryerStrategy = retryerStrategy;
        this.predicateResult = predicateResult;
        this.predicateThrowsException = predicateThrowsException;
        this.retryerStrategy.setRetryerLister(retryerLister);
        this.executor = executor;
    }


    public <T> T exec(RetryerSupplier supplier){
        T result = null;
        try {
            result = (T) supplier.exec();
            if (shouldRetry(result)) {
                    if (null != executor) {
                            executor.execute(()->{
                                if (execRetryStrategy()) {
                                    asyncExec(supplier);
                                }
                            });
                    } else {
                        if (execRetryStrategy()){
                            exec(supplier);
                        }
                    }
            }
        } catch (Throwable e) {
            if (e.getClass().equals(RuntimeException.class)){
                e = e.getCause();
            }
            if (shouldRetry(e)) {
                    if (null != executor) {
                            executor.execute(()->{
                                if (execRetryStrategy()) {
                                    asyncExec(supplier);
                                }
                            });
                    } else {
                        if (execRetryStrategy()) {
                            exec(supplier);
                        }
                    }
                }
        }
        reset();
        return result;
    }

    // reset
    private void reset(){
        retryerStrategy.reset();
    }

    private <T> void asyncExec(RetryerSupplier supplier) {

            try {
                T result = (T) supplier.exec();
                if (shouldRetry(result)) {
                    if (execRetryStrategy()) {
                        asyncExec(supplier);
                    }
                }
            } catch (Throwable e) {
                if (e instanceof RuntimeException) {
                    e = e.getCause();
                }
                if (shouldRetry(e)) {
                    if (execRetryStrategy()) {
                        asyncExec(supplier);
                    }
                }
            }

    }
    private boolean execRetryStrategy() {
        return retryerStrategy.exec1();
    }

    private boolean shouldRetry(Object result) {
        return null != predicateResult && ((IPredicate) predicateResult).apply(result);
    }

    private boolean shouldRetry(Throwable e) {
        return null != predicateThrowsException && ((IPredicate) predicateThrowsException).apply(e);
    }

}
